// 防抖
/**
 * 
 * @param {function} func - 防抖的函数
 * @param {number} wait - 等待时间
 * @param {boolean} immediate - 是否立即执行
 */
export function debounce(func, wait, immediate) {
  var timeout, result;
  var debounced = function () {
    var context = this;
    var args = arguments;

    if (timeout) clearTimeout(timeout);
    if (immediate) {
      // 如果已经执行过，不再执行
      var callNow = !timeout;
      timeout = setTimeout(function () {
        timeout = null;
      }, wait)
      if (callNow) result = func.apply(context, args)
    }
    else {
      timeout = setTimeout(function () {
        func.apply(context, args)
      }, wait);
    }
    return result;
  };

  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
}

// 节流
/* 
  三种用法
  throttle(func, wait)
  throttle(func, wait, {leading: false})
  throttle(func, wait, {trailing: false})
*/
/**
 * 节流
 * 
 * @param {function} func - 节流的函数
 * @param {number} wait - 等待时间，单位毫秒
 * @param {object} options - {leading: false} {trailing: false} 二选一
 * leading:false 刚开始不触发，到达等待时间后才触发
 * trailing:false 刚开始触发，到达等待时间后不触发
 */
export function throttle(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  if (!options) options = {};

  var later = function () {
    previous = options.leading === false ? 0 : +new Date();
    timeout = null;
    func.apply(context, args);
    if (!timeout) context = args = null;
  };

  var throttled = function () {
    var now = +new Date();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
  };

  throttled.cancel = function () {
    clearTimeout(timeout);
    previous = 0;
    timeout = null;
  };

  return throttled;
}

/**
 * 数组去重
 * 
 * @param {array} array
 * @param {boolean} isSorted 是否已经排序
 * @param {function} iteratee 一个函数，可以对每个元素进行重新计算
 * 
 * @return {array} 去重后的数组
 */
export function unique(array, isSorted, iteratee) {
  var res = [];
  var seen = [];

  for (var i = 0; i < array.length; i++) {
    var value = array[i];
    var computed = iteratee ? iteratee(value, i, array) : value;
    if (isSorted) {
      if (i === 0 || seen !== computed) {
        res.push(value);
      }
      seen = computed;
    } else if (iteratee) {
      if (seen.indexOf(computed) === -1) {
        seen.push(computed);
        res.push(value);
      }
    } else if (res.indexOf(value) === -1) {
      res.push(value);
    }
  }
  return res;
}

/**
 * 数组去重ES5,支持对象数组,这种是最强的去重
 */
export function uniqueES5 (array) {
  let obj = {};
  return array.filter(item => obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : obj[typeof item + JSON.stringify(item)] = true)
}

/**
 * 数组去重ES6, 不支持对象数组
 */
export const uniqueES6 = (array) => [...new Set(array)];

/**
 * 判断数据类型方法 typeof + Object.prototype.toString.call()
 */
var class2type = {};
// 生成 class2type 映射
'Boolean Number String Function Array Date RegExp Error Null Undefined'.split(' ').map(item => {
  class2type[`[object ${item}]`] = item.toLowerCase();
})

export function type (obj) {
  // 解决IE6 null也是object的问题
  if (obj === null) {
    return obj + '';
  }
  return typeof obj === 'object' || typeof obj === 'function' ? class2type[Object.prototype.toString.call(obj)] || 'object' : typeof obj;
}

/**
 * 判断对象是否为空
 * @param {Object} obj 对象
 * 
 */
export function isEmptyObject (obj) {
  var name;
  for (name in obj) {
    return false
  }
  return true;
}

/**
 * 判断是否是window对象
 * @param {Object} obj 
 */
export function isWindow (obj) {
  return obj !== null && obj === obj.window;
}

// isArrayLike
/**
 * 判断是否是数组e
 * @param {Array} obj 
 */
export function isArrayLike (obj) {
  // obj 必须有 length 属性
  var length = !!obj && 'length' in obj && obj.length;
  var typeRes = type(obj);

  // 排除掉函数和 Window 对象
  if (typeRes === 'function' || isWindow(obj)) {
    return false;
  }

  return typeRes === 'array' || length === 0 || typeof length === 'number' && length > 0 && (length - 1) in obj;
}

/**
 * 判断是不是DOM元素
 * @param {Object} obj
 */
export function isElement (obj) {
  return !!(obj && obj.nodeType === 1);
}

/**
 * 判断是否是function
 * @param {Object} obj
 */
export function isFunction (obj) {
  return type(obj) === 'function';
}

/**
 * 是否是一个纯粹的对象
 * @param {Object} obj
 */
// 存放toString映射结果的对象
var class2type = {};
// 相当于 Object.prototype.toString
var toString = class2type.toString;
// 相当于 Object.prototype.hasOwnProperty
var hasOwn = class2type.hasOwnProperty;

// 是否是纯粹的对象
export function isPlainObject (obj) {
  var proto, Ctor;

  // 排除掉明显不是obj的以及一些宿主对象如window
  if (!obj || toString.call(obj) !== '[object Object]') {
    return false;
  }

  /**
   * getPrototypeOf es5 方法，获取obj的原型
   * 以 new Object 创建的对象为例的话
   * obj.__proto__ === Object.prototype
  */
  proto = Object.getPrototypeOf(obj);

  // 没有原型的对象是纯粹的， Object.create(null) 就在这里返回true
  if (!proto) {
    return true;
  }

  /**
   * 以下判断通过 new Object 方式创建的对象
   * 判断 proto 是否有 constructor 属性，如果有就让 Ctor 的值为 proto.constructor
   * 如果是 Object 函数创建的对象，Ctor 在这里就等于 Object 构造函数
   */
  Ctor = hasOwn.call(proto, 'constructor') && proto.constructor;

  // 在这里判断Ctor构造函数是不是Object构造函数，用于区分自定义构造函数和Object构造函数
  return typeof Ctor === 'function' && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}

/**
 * 浅拷贝，创建一个新对象进行赋值
 * @param {Object} obj
 */
export function shallowCopy (obj) {
  // 只对对象生效
  if (typeof obj !== 'object') return;
  // 根据obj的类型创建对象或数组
  var newObj = obj instanceof Array ? [] : {};
  // 只对obj的属性进行赋值
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

/**
 * 深拷贝，如果是对象就递归调用深拷贝
 * @param {object} obj
 */
export function deepCopy (obj) {
  if (typeof obj !== 'object') return;
  var newObj = obj instanceof Array ? [] : {};
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
    }
  }
}

/**
 * extend 拷贝
 * @param {Boolean} deep - 是否进行深拷贝
 * @param {Object} obj - 对象，可选择传多个
 */

export function extend () {
  // 默认不进行深拷贝
  var deep = false;
  var name, options, src, copy, clone, copyIsArray;
  var length = arguments.length;
  // 记录要复制的对象下标
  var i = 1;
  // 第一个参数不传布尔值的情况下，target默认是第一个参数
  var target = arguments[0] || {};
  // 如果第一个参数是布尔值，第二个参数是target
  if (typeof target === 'boolean') {
    deep = target;
    target = arguments[i] || {};
    i++;
  }
  // 如果target不是对象，我们是无法进行复制的，所以设为{}
  if (typeof target !== 'object' && isFunction(target)) {
    target = {};
  }

  // 循环遍历要复制的对象
  for (; i < length; i++) {
    // 获取当前对象
    options = arguments[i];
    // 要求不能为空，避免extend(a,,b)这种情况
    if (options != null) {
      for (name in options) {
        // 目标属性值
        src = target[name];
        // 要复制的对象的属性值
        copy = options[name];

        // 解决循环引用
        if (target === copy) {
          continue;
        }

        // 要递归的对象必须是 plainObject 或者数组
        if (deep && copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
          // 要复制的对象属性值类型需要与目标属性值相同
          if (copyIsArray) {
            copyIsArray = false;
            clone = src && Array.isArray(src) ? src : [];
          } else {
            clone = src && isPlainObject(src) ? src : {};
          }

          target[name] = extend(deep, clone, copy);
        } else if (copy !== undefined) {
          target[name] = copy
        }
      }
    }
  }
  return target;
}

/* ***** 数组扁平化 ***** */
/**
 * 数组扁平化基础函数
 * @param {Array} input         要处理的数组
 * @param {Boolean} shallow     是否只扁平化一层
 * @param {Boolean} strict      是否严格处理元素
 * @param {Array} output        为了方便递归而递归的参数
 */
export function flatten (input, shallow, strict, output) {
  // 递归使用的时候会用到output
  output = output || [];
  var idx = output.length;

  for (var i = 0, len = input.length; i < len; i++) {
    var value = input[i];
    // 如果是数组，就进行处理
    if (Array.isArray(value)) {
      // 如果是只扁平一层，遍历该数组，依次填入output
      if (shallow) {
        var j = 0;
        var length = value.length;
        while (j < length) output[idx++] = value[j++];
      }
      // 如果是全部扁平就递归，传入已经处理的output，递归中接着处理output
      else {
        flatten(value, shallow, strict, output);
        idx = output.length;
      }
    }
    // 不是数组，根据strict的值判断是跳过不处理还是放入output
    else if (!strict) {
      output[idx++] = value;
    }
  }

  return output;
}

export const _ = flatten;

// 正常扁平化
_.flatten = function (array, shallow) {
  return flatten(array, shallow, false);
}
// union,传入过个数组返回并集 [1,2,3], [101, 2, 1,10],[2,1] -> [1,2,3,101,10]
_.union = function () {
  return Array.from(new Set(flatten(arguments, true, true)));
}
// difference
// _.difference([1, 2, 3, 4, 5], [5, 2, 10], [4], 3); => [1, 3]
export function difference (array, ...rest) {
  rest = flatten(rest, true, true);
  return array.filter(item => rest.indexOf(item) === -1);
}

/**
 * 实现一个findIndex
 * @param {Array} array 数组
 * @param {Function} predicate 条件函数
 * @param {Object} context 执行上下文
 */
// export function findIndex (array, predicate, context) {
//   for (var i = 0; i < array.length; i++) {
//     if (predicate.call(context, array[i], i, array)) return i;
//   }
//   return -1;
// }

/**
 * 实现findLastIndexOf
 * @param {Array} array 数组
 * @param {Function} predicate 条件函数
 * @param {Object} context 执行上下文
 */
// export function findLastIndexOf (array, predicate, context) {
//   var length = array.length;
//   for (var i = length - 1; i >= 0; i--) {
//     if (predicate.call(context, array[i], i, array)) return i;
//   }
//   return -1;
// }

/**
 * 创建一个查找方法
 * @param {Number} dir 步长
 */
function createIndexFinder (dir) {
  return function (array, predicate, context) {
    var length = array.length;
    var index = dir > 0 ? 0 : length - 1;
    for (; index >= 0 && index < length; index += dir) {
      if (predicate.call(context, array[index], index, array)) return index;
    }
    return -1;
  }
}

/**
 * @param {Array} array 数组
 * @param {Function} predicate 条件函数
 * @param {Object} context 执行上下文
 * findIndex(arr, item => item > 3)
 */
// 正序查找
export const findIndex = createIndexFinder(1);
// 倒序查找
export const findLastIndex = createIndexFinder(-1);

/**
 * 在排好序的数组中找到value应该插入的位置，二分查找
 * @param {Array} array 数组，或对象数组
 * @param {Object} obj value的值或对象,如果是对象 iteratee 要指定对象的属性
 * @param {Function} iteratee 迭代器
 * @param {Object} context 执行上下文
 * 使用：sortedIndex(arr2, 25, item => item)
 * 使用：sortedIndex(arr2, {name: 'sdfsdff', age: 18}, item => item.age)
 */
export function sortedIndex (array, obj, iteratee, context) {
  iteratee = cb(iteratee, context);
  var low = 0;
  var high = array.length;
  while (low < high) {
    var mid = Math.floor((low + high) / 2);
    if (iteratee(array[mid]) < iteratee(obj)) low = mid + 1;
    else high = mid;
  }
  return high;
}
// 回调函数
function cb (func, context) {
  if (context === void 0) return func;
  return function () {
    return func.apply(context, arguments);
  };
}

/**
 * 
 * @param {Number} dir 步长
 */
function createIndexOfFinder (dir, predicate, sortedIndex) {
  return function (array, item, idx) {
    var length = array.length;
    var i = 0;
    if (typeof idx === 'number') {
      if (dir > 0) {
        // 正向
        i = idx >= 0 ? idx : Math.max(length + idx, 0);
      } else {
        // 反向
        length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
      }
    }
    else if (sortedIndex && idx && length) {
      idx = sortedIndex(array, item);
      // 如果该插入的位置的值正好等于元素的值，说明是第一个符合要求的值
      return array[idx] === item ? idx : -1;
    }

    // 判断元素是否是 NaN
    if (item !== item) {
      // 在截取好的数组中查找第一个满足isNaN函数的元素下标
      idx = predicate(array.slice(i, length), isNaN);
      return idx >= 0 ? idx + i : -1;
    }

    for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
      if (array[idx] === item) return idx;
    }
    return -1;
  }
}

/**
 * @param {Array} arr 数组
 * @param {Number} num 元素
 * @param {Object} context 执行上下文
 * indexOf(arr, 3)
 */
export const indexOf = createIndexOfFinder(1, findIndex, sortedIndex);
export const lastIndexOf = createIndexOfFinder(-1, findLastIndex);

/**
 * 实现each功能
 * @param {Object} obj 数组或json
 * @param {Function} callback 回调
 * 用法 each(arr, item => {...})
 * call会造成性能损失
 */
export function each (obj, callback) {
  var length, i = 0;
  if (isArrayLike(obj)) {
    length = obj.length;
    for (; i < length; i++) {
      if (callback.call(obj[i], obj[i], i) === false) break;
    }
  }
  else {
    for (i in obj) {
      if (callback.call(obj[i], obj[i], i) === false)  break;
    }
  }
  return obj;
}

/**
 * @param {Object} a
 * @param {Object} b
 */
export function eq (a, b) {}

/**
 * 柯里化
 * 柯里化理解：用闭包把参数保存起来，当参数的数量足够执行函数了，就开始执行函数。
 * @param {Function} fn 柯里化的函数
 * @param {Object} args 参数
 * var fn = curry((a, b) => console.log([a, b]))
 * fn(a)(b)
 * fn(a, b)
 */
export function curry (fn, args) {
  var length = fn.length;
  args = args || [];
  return function () {
    var _args = args.slice(0);
    var arg = null;
    var i = 0;
    for (; i < arguments.length; i++) {
      arg = arguments[i];
      _args.push(arg);
    }
    if (_args.length < length) {
      return curry.call(this, fn, _args);
    } else {
      return fn.apply(this, _args);
    }
  }
}

// 闭包实现保存参数 ES6
function partial (fn, ...args) {
  return function (...outerArgs) {
    return fn.call(this, ...args, ...outerArgs)
  }
}

// ES5
function partialES5 (fn) {
  var args = [].slice.call(arguments, 1);
  return function () {
    var newArgs = args.concat([].slice.call(arguments));
    return fn.apply(this, newArgs);
  }
}

/**
 * 乱序数组，比 sort + random 更合理，因为sort的排序方法会根据环境不同而变化
 * @param {Array} arr 
 */
export function shuffle (arr) {
  for (let i = arr.length; i; i--) {
    let j = Math.floor(Math.random() * i);
    [arr[i - 1], arr[j]] = [arr[j], arr[i - 1]];
  }
  return arr;
}
